<?php
namespace app\manage\service;

/**
 * 所有API类的超级父类
 * @author 李全春 <adv30@126.com> <time:>
 */
class ApiSuper{

    protected $sessionId = null;    //app接口访问者传过来的sessionId
    protected $textData  = null;    //接收到的文本数据
    protected $fileData  = null;    //接收到的文件数据

    protected $sendTime;            //发送方发送时的时间戳
    protected $encryption       = 'off';      //是否加密 两个值 on||off,其他的值报错,on则解密对应的文本数据，返回的数据也加密
    protected $operation        = '';         //DECODE 解密   其它为加密
    protected $key              = '';         //用来解密字串的密码
    protected $expiry           = 0;          //密文从生成之时起的有效时间,单位：秒
    protected $cKeyLength       = 4;          //动态密码的长度,默认为4.可以根据传过来的时间戳动态计算

    protected $res              = [];       //执行动作之后返回的结果
    protected $status           = NULL_DATA_STATUS;        //给App返回的状态
    protected $code             = NULL_DATA_CODE;

    protected $statusInfoArr    = [
            NULL_DATA_STATUS => '没有数据返回!',
            SUCCESS_STATUS   => '返回正常数据!',
            ERR_STATUS       => '错误提示信息:'          //状态为2时再输出erroInfo信息
        ];

    protected $msg              = '';       //返回的消息字串
    protected $err              = 'noErr';  //当前错误信息提示
    protected $errInfoArr       = [
        'noErr'         => '没有找到错误!',
        'time'          => '请校对您的手机时间',
        'ver'           => '请求的接口版本不存在',
        'appkey'        => 'appkey错误',
        'module'        => 'module模块不存在',
        'controller'    => 'controller控制器不存在',
        'action'        => 'action方法不可被调用',
        'encryption'    => 'encryption加密参数错误',
        'param'         => '参数丢失',
        ];
    protected $arrData = [];
    //生成签名的常用规则
    public function signPublic( $sendTime, $ver, $encryption, $module, $controller, $action ): string
    {
        $keyA = substr( $sendTime, 0, 5 );
        $keyB = substr( $sendTime, 5, 5 );
        return md5( $ver . $keyA . $encryption . $module . $keyB . $controller . $action );
    }

    //加密或解密数据
    public function authCode( $str, $operation, $key, $expiry=0 ) 
    {

        // $str        = $this->textData;
        // $operation  = $this->operation;
        // $key        = $this->key;
        // $expiry     = $this->expiry;
        // 动态密匙长度，相同的明文会生成不同密文就是依靠动态密匙
        $cKeyLength = 4;
        // 密匙
        $key        = md5($key ? $key . 'thinklev' : $this->sendTime . 'thinklev');
        // 密匙a会参与加解密
        $keya       = md5(substr($key, 0, 16));
        // 密匙b会用来做数据完整性验证
        $keyb       = md5(substr($key, 16, 16));
        // 密匙c用于变化生成的密文
        $keyc       = $cKeyLength ? ($operation == 'DECODE' ? substr($str, 0, $cKeyLength) : substr(md5(microtime()), -$cKeyLength)) : '';

        // 参与运算的密匙
        $cryptkey   = $keya . md5($keya . $keyc);
        $keyLength  = strlen($cryptkey);
        // 明文，前10位用来保存时间戳，解密时验证数据有效性，10到26位用来保存$keyb(密匙b)，
        //解密时会通过这个密匙验证数据完整性
        // 如果是解码的话，会从第$cKeyLength位开始，因为密文前$cKeyLength位保存 动态密匙，以保证解密正确
        $str        = $operation == 'DECODE' 
                        ? (substr($str, $cKeyLength)) 
                        : sprintf('%010d', $expiry ? $expiry + NOW : 0).substr(md5($str.$keyb), 0, 16).$str;
        $strLength  = strlen($str);
//pre($str,$strLength,chr(67),ord($str[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
        $result     = '';
        $box        = range(0, 255);
        $rndkey     = array();
        // 产生密匙簿
        for($i = 0; $i < 256; $i++) {
            $rndkey[$i] = ord($cryptkey[$i % $keyLength]);
        }

        // 用固定的算法，打乱密匙簿，增加随机性，好像很复杂，实际上对并不会增加密文的强度
        for($j = $i = 0; $i < 256; $i++) {
            $j       = ($j + $box[$i] + $rndkey[$i]) % 256;
            $tmp     = $box[$i];
            $box[$i] = $box[$j];
            $box[$j] = $tmp;
        }

        // 核心加解密部分
        for($a = $j = $i = 0; $i < $strLength; $i++) {
            $a       = ($a + 1) % 256;
            $j       = ($j + $box[$a]) % 256;
            $tmp     = $box[$a];
            $box[$a] = $box[$j];
            $box[$j] = $tmp;
            // 从密匙簿得出密匙进行异或，再转成字符

           $result  .= chr(ord($str[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
        }

        if($operation == 'DECODE') {
            // 验证数据有效性，请看未加密明文的格式
            if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - NOW > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
                return substr($result, 26);
            } else {
                return '';
            }
        } else {
            // 把动态密匙保存在密文里，这也是为什么同样的明文，生产不同密文后能解密的原因
            // 因为加密后的密文可能是一些特殊字符，复制过程可能会丢失，所以用base64编码
            return $keyc . base64_encode($result);
        }
    }
    // 返回运行后的数据
    public function ajaxReturn(): array
    {
        $result['status'] = $this->status;
        $result['code']   = $this->code;
        $result['msg']    = ITEM_NAME . $this->msg;
        $result['res']    = $this->res;

        //记录日志
        if( APP_DEBUG ){
            $logData['module']      = input('module');
            $logData['controller']  = input('controller');
            $logData['action']      = input('action');
            $logData['textData']    = input('textData', '','');
            $logData['encryption']  = input('encryption');
            $logData['sendTime']    = input('sendTime');
            $logData['expiry']      = input('expiry');
            $logData['appkey']      = input('appkey');
            $logData['resolve_val'] = serialize($this->arrData);
            $logData['result_status']      = $this->status;
            $logData['result_msg']         = $this->msg;
            $logData['result_res']         = serialize($this->res);
            // TODO 暂时先注释掉
            // $logId = module('Api_1/ApiClientLog')->addLog($logData);
        }
        return $result;
    }
}
